import { PropType, App } from 'vue'
import { defineComponent, getCurrentInstance, reactive, ref, computed, watch } from 'vue'
import { useEventListener } from '@vueuse/core'
import { findDOMNode } from '../_util/props-util'
import * as utils from './utils'
import Marks from './Marks'
import Steps from './Steps'
import Hanlde from './Hanlde'

const prefixCls = 'x-slider'

const Slider = defineComponent({
  name: prefixCls,
  props: {
    value: [Number, Array] as PropType<number | Array<number>>,
    disabled: Boolean,
    step: { default: 1 },
    min: { default: 0 },
    max: { default: 100 },
    range: Boolean,
    marks: Object,
    formatTooltip: Function as PropType<(value: number) => any>,
    tooltip: { default: true }
  },
  emits: ['update:value', 'change'],
  setup(props, { attrs, emit }) {
    const { proxy: self } = getCurrentInstance()
    const sliderRef = ref<HTMLDivElement>()
    const _value = computed(() => Array.isArray(props.value) ? props.value : [props.value || 0])
    const state = reactive({
      value: _value.value
    })
    watch(_value, val => {
      if (val.toString() === state.value.toString()) return
      state.value = val
    })


    // ========================== Calc ==========================
    function getSliderLength() {
      if (!sliderRef.value) return 0
      return sliderRef.value.getBoundingClientRect().width
    }
    function calcValue(offset: number) {
      const ratio = offset / getSliderLength()
      let v = props.min + (props.max - props.min) * ratio
      return utils.ensureValueInRange(v, props)
    }
    function calcValueByOffset(offset: number) {
      return trimAlignValue(calcValue(offset))
    }
    function getClosestHandle(val: number) {
      const arr = state.value
      const i = arr.findIndex(e => val < e) - 1
      if (i == -1) return 0
      if (i == -2) return arr.length - 1
      const a = val - arr[i], b = arr[i + 1] - val
      if (a === b) return currHandle
      return a < b ? i : i + 1
    }
    function trimAlignValue(val: number) {
      val = utils.ensureValueInRange(val, props)
      return utils.ensureValuePrecision(val, props)
    }


    // ========================== Node ==========================
    const handleRefs = []
    function saveHanels(vm) {
      handleRefs.push(findDOMNode(vm))
    }
    function getHanleNode() {
      return state.value.map(e => {
        const { min, max, disabled, formatTooltip, tooltip } = props
        return (
          <Hanlde value={e} ref={saveHanels} {...{ prefixCls, min, max, disabled, formatTooltip, tooltip }} onMousedown={onMousedown} onMouseup={onMouseup}></Hanlde>
        )
      })
    }
    function getBarNode() {
      const { min, max } = props, len = max - min
      let style: any = {}
      if (props.range) {
        style.left = `${(state.value[0] - min) / len * 100}%`
        style.right = `${100 - (state.value[1] - min) / len * 100}%`
      } else {
        style.left = `0`
        style.right = `${100 - (state.value[0] - min) / len * 100}%`
      }
      const barProps = {
        class: [`${prefixCls}_bar`],
        style
      }
      return <div {...barProps}></div>
    }


    // ========================== Event ==========================
    useEventListener(sliderRef, 'mousedown', onMousedown)
    useEventListener(sliderRef, 'touchstart', onMousedown)

    let startPos, startOffset, currHandle
    function onClickLabel(e: Event, value: number) {
      const handle = getClosestHandle(value)
      state.value[handle] = value
      onChange(state.value)
    }
    function onMousedown(e: MouseEvent | TouchEvent) {
      e.preventDefault()
      e.stopPropagation()
      onMouseup()
      const pageX = 'pageX' in e ? e.pageX : e.touches[0]?.pageX
      const target = e.target as HTMLElement
      currHandle = handleRefs.findIndex(e => e === target)
      if (currHandle > -1) {
        startOffset = target.offsetLeft
        target.focus()
      } else {
        startOffset = pageX - sliderRef.value.getBoundingClientRect().left
        const val = calcValueByOffset(startOffset)
        currHandle = getClosestHandle(val)
        state.value[currHandle] = val
        onChange(state.value)
        handleRefs[currHandle].focus()
      }
      startPos = pageX
      window.addEventListener('mousemove', onMousemove, { passive: true })
      window.addEventListener('mouseup', onMouseup, { passive: true })
      window.addEventListener('touchmove', onMousemove, { passive: true })
      window.addEventListener('touchend', onMouseup, { passive: true })
    }
    function onMousemove(e: MouseEvent | TouchEvent) {
      const pageX = 'pageX' in e ? e.pageX : e.touches[0]?.pageX
      const offset = pageX - startPos + startOffset, i = currHandle
      let v = calcValueByOffset(offset)
      if (v < state.value[currHandle - 1]) {
        [state.value[currHandle - 1], state.value[currHandle]] = [v, state.value[currHandle - 1]]
        currHandle--
      } else if (v > state.value[currHandle + 1]) {
        [state.value[currHandle + 1], state.value[currHandle]] = [v, state.value[currHandle + 1]]
        currHandle++
      } else {
        state.value[currHandle] = v
      }
      onChange(state.value)
      if (i != currHandle) {
        // toogle handle
        Object.defineProperty(e, 'target', { writable: true });
        // @ts-ignore
        e.target = handleRefs[currHandle]
        onMousedown(e)
      }
    }
    function onMouseup() {
      handleRefs.forEach(e => e.blur())
      window.removeEventListener('mousemove', onMousemove)
      window.removeEventListener('mouseup', onMouseup)
      window.removeEventListener('touchmove', onMousemove)
      window.removeEventListener('touchend', onMouseup)
    }
    function onChange(value: number | number[]) {
      value = props.range ? value : value[0]
      emit('update:value', value)
      emit('change', value)
    }

    // ========================== Render ==========================
    return () => {
      const sliderProps = {
        ref: sliderRef,
        class: [prefixCls, {
          [`${prefixCls}-marks`]: !!props.marks,
          [`${prefixCls}-disabled`]: props.disabled
        }],
      }

      return (
        <div {...sliderProps}>
          {getBarNode()}
          {/* <Steps prefixCls={prefixCls} marks={props.marks}></Steps> */}
          <Marks prefixCls={prefixCls} {...props} onClickLabel={onClickLabel}></Marks>
          {getHanleNode()}
        </div>
      )
    }
  }
})


Slider.install = (app: App) => {
  app.component(Slider.name, Slider)
}

export default Slider